#ifndef UTILITY_H_
#define UTILITY_H_
#include <chrono>
#include <filesystem>
#include <fstream>
#include <iterator>
#include <string>
#include <vector>
namespace autodrive {
namespace perception {
class Timer {
 public:
  explicit Timer() noexcept
      : m_begin_(std::chrono::high_resolution_clock::now()){};
  ~Timer() = default;
  Timer(Timer const&) noexcept = default;
  Timer& operator=(Timer const&) noexcept = default;
  Timer(Timer&&) noexcept = default;
  Timer& operator=(Timer&&) noexcept = default;

  template <typename Duration = std::chrono::microseconds>
  int64_t elapsed() const {
    return std::chrono::duration_cast<Duration>(
               std::chrono::high_resolution_clock::now() - m_begin_)
        .count();
  }

 private:
  std::chrono::time_point<std::chrono::high_resolution_clock> m_begin_;
};

inline std::string to_green(std::string str) noexcept {
  return "\033[1;32m" + str + "\033[0m";
}

inline std::string to_red(std::string str) noexcept {
  return "\033[1;5;31m" + str + "\033[0m";
}

inline std::string to_yellow(std::string str) noexcept {
  return "\033[1;5;33m" + str + "\033[0m";
}

constexpr long long int operator"" _GiB(unsigned long long val) {
  return val * (1 << 30);
}
constexpr long long int operator"" _MiB(unsigned long long val) {
  return val * (1 << 20);
}
constexpr long long int operator"" _KiB(unsigned long long val) {
  return val * (1 << 10);
}

/// @brief read text file
/// @tparam T data type
/// @param path file path
/// @param obj data object
/// @param err error message
/// @return false on error
template <typename T>
void read_txt_data(std::filesystem::path const& path,
                   std::vector<T>& obj) noexcept(false) {
  static_assert(std::is_arithmetic_v<T>, "type must arithmetic");
  if (!std::filesystem::exists(path)) {
    throw std::runtime_error("file " + path.string() + " does not exsit");
  }
  std::ifstream iStream{path};
  if (!iStream.is_open()) {
    throw std::runtime_error("failed to open file " + path.string());
  }
  obj = {std::istream_iterator<T>(iStream), std::istream_iterator<T>()};
}

/// @brief read binary file
/// @tparam T data type
/// @param path file path
/// @param err error message
/// @return false on error
template <typename T>
void read_bin_data(std::filesystem::path const &path,
                   std::vector<T> &data) noexcept(false) {
  static_assert(std::is_arithmetic_v<T>, "clip type is not arithmetic type");
  if (!std::filesystem::exists(path)) {
    throw std::runtime_error("file " + path.string() + " does not exsit");
  }

  std::ifstream iStream{path, std::ios::binary};
  if (!iStream.is_open()) {
    throw std::runtime_error("failed to open file " + path.string());
  }
  iStream.seekg(0, iStream.end);
  auto sizeByte{iStream.tellg()};
  auto rest{sizeByte % sizeof(T)};
  if (rest != 0) {
    throw std::runtime_error("size length can not align to sizeof(T)");
  }

  auto dataSize{sizeByte / sizeof(T)};
  data.clear();
  data.resize(dataSize);

  iStream.seekg(0, iStream.beg);
  iStream.read(reinterpret_cast<char *>(data.data()), sizeByte);
}

/// @brief clip data in range
/// @tparam T output data type
/// @param data data to clip
/// @return clipped data
template <typename T>
T clip(double data) {
  static_assert(std::is_arithmetic_v<T>, "clip type is not arithmetic type");
  bool isNegative{false};
  if (std::is_floating_point_v<T> && data < 0) {
    data = -data;
    isNegative = true;
  }

  auto value{std::clamp<double>(data, std::numeric_limits<T>::min(),
                                std::numeric_limits<T>::max())};
  if (isNegative) {
    value = -value;
  }
  return static_cast<T>(value);
}

}  // namespace perception
}  // namespace autodrive

#endif  // !UTILITY_H_
